﻿using CTP.CRM.Core.Redis;
using CTP.CRM.Repository.Enum;
using CTP.CRM.Repository.Setting.Entitys;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Emit;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp;
using Dapper;
using CTP.CRM.Core.Common.Constants;

namespace CTP.CRM.Repository.Setting.Services
{
    public class SettingRepository : ISettingRepository, IScopedDependency
    {
        readonly ISqlSugarClient _context;

        public SettingRepository()
        {
            _context = SqlsugarScopeBasic.GetContextSafely("default");
        }

        public Task<int> UpsertSetting(IEnumerable<KeyValuePair<string, string>> keyValuePairs, int foreignId,
            SettingTypeEnum type)
        {
            var entities = new List<SettingEntity>();
            const int valueMaxLength = 512;
            string sql =
                "insert into t_setting(Type,`Key`,Value,ForeignKey,UpdateTime) values (@Type,@Key,@Value,@ForeignKey,now()) on duplicate key update Value=@Value,UpdateTime=now();";

            foreach (var item in keyValuePairs)
            {
                if (item.Key.IsNullOrEmpty())
                {
                    throw new BusinessException("400", "Key不能为空");
                }

                if (!string.IsNullOrEmpty(item.Value) && item.Value.Length > valueMaxLength)
                {
                    throw new BusinessException("400", $"Value长度不能超过{valueMaxLength}");
                }

                var itemEntity = new SettingEntity
                {
                    ForeignKey = foreignId,
                    Type = type,
                    Key = item.Key,
                    Value = item.Value,
                    UpdateTime = DateTime.Now,
                };

                entities.Add(itemEntity);
            }

            var cacheKey = GetSettingKey(foreignId, type);
            RedisHelper.Client.DelAsync(cacheKey);
            return _context.Ado.Connection.ExecuteAsync(sql, entities);
        }

        public Task<int> DeleteSetting(IEnumerable<int> deleteIds)
        {
            return _context.Deleteable<SettingEntity>().Where(val => deleteIds.Contains(val.Id)).ExecuteCommandAsync();
        }

        /// <summary>
        ///  获取设置
        /// </summary>
        /// <param name="foreignId"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public async Task<List<SettingEntity>> GetSetting(int foreignId, SettingTypeEnum type)
        {
            var sql = "select * from t_setting where ForeignKey=@foreignId and Type=@type";
            var result = await _context.Ado.Connection.QueryAsync<SettingEntity>(sql, new { foreignId, type });
            return result.ToList();
        }

        private async Task<Dictionary<int, List<SettingEntity>>> GetUserSetting(List<int> userids)
        {
            var sql = "select * from t_setting where ForeignKey in @userids and Type=4";
            var result = await _context.Ado.Connection.QueryAsync<SettingEntity>(sql, new { userids });
            var userSettingDic = result.GroupBy(x => x.ForeignKey).ToDictionary(x => x.Key, x => x.ToList());
            if (userSettingDic.Count != userids.Count)
            {
                var notExistUserIds = userids.Except(userSettingDic.Keys).ToList();
                foreach (var notExistUserId in notExistUserIds)
                {
                    userSettingDic.Add(notExistUserId, null);
                }
            }

            return userSettingDic;
        }

        /// <summary>
        /// 批量的支持，避免并行请求时，重复请求数据库
        /// </summary>
        /// <param name="foreignIdList"></param>
        /// <param name="type"></param>
        /// <param name="instance"></param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        /// <exception cref="BusinessException"></exception>
        public async Task<List<T>> GetSettingValueListAsync<T>(List<int> foreignIdList, SettingTypeEnum type, T instance = default) where T : new()
        {
            List<T> result = new List<T>();
            var settingDic = await GetUserSetting(foreignIdList);
            foreach (var kvpitem in settingDic)
            {
                var settings = kvpitem.Value;
                var cacheKey = GetSettingKey(kvpitem.Key, type);
                var cacheValue = RedisHelper.Client.Get<T>(cacheKey);
                if (cacheValue == null)
                {
                    var action = SettingValueAction<T>();
                    if (instance == null && typeof(T).GetConstructor(Type.EmptyTypes) == null)
                    {
                        throw new BusinessException("400", "如果T没有无参构造函数，请传入instance");
                    }

                    if (settings == null || settings.Count == 0)
                    {
                        // 执行无参构造函数
                        cacheValue = instance ?? Activator.CreateInstance<T>();
                        RedisHelper.Client.Set(cacheKey, cacheValue, TimeSpan.FromSeconds(60));
                    }
                    else
                    {
                        cacheValue = action(instance, settings);
                        RedisHelper.Client.Set(cacheKey, cacheValue, TimeSpan.FromHours(4));
                    }
                }
                result.Add(cacheValue);
            }


            return result;
        }

        /// <summary>
        /// 获取设置的值
        /// </summary>
        /// <param name="foreignId"></param>
        /// <param name="type"></param>
        /// <param name="instance">如果不支持new()，则需要自己创建一个模型，或者定义初始值</param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public async Task<T> GetSettingValueAsync<T>(int foreignId, SettingTypeEnum type, T instance = default)
        {
            var cacheKey = GetSettingKey(foreignId, type);
            var cacheValue = RedisHelper.Client.Get<T>(cacheKey);
            if (cacheValue == null)
            {
                using (var locked = RedisHelper.Client.Lock(cacheKey, 10))
                {
                    if (locked == null)
                    {
                        throw new BusinessException("400", "获取用户设置信息失败，分布式锁获取失败");
                    }

                    cacheValue = RedisHelper.Client.Get<T>(cacheKey);
                    if (cacheValue == null)
                    {
                        var settings = await GetSetting(foreignId, type);
                        var action = SettingValueAction<T>();
                        if (instance == null && typeof(T).GetConstructor(Type.EmptyTypes) == null)
                        {
                            throw new BusinessException("400", "如果T没有无参构造函数，请传入instance");
                        }

                        if (settings == null || settings.Count == 0)
                        {
                            // 执行无参构造函数
                            cacheValue = instance ?? Activator.CreateInstance<T>();
                            RedisHelper.Client.Set(cacheKey, cacheValue, TimeSpan.FromSeconds(10));
                        }
                        else
                        {
                            cacheValue = action(instance, settings);
                            RedisHelper.Client.Set(cacheKey, cacheValue, TimeSpan.FromHours(4));
                        }
                    }
                }
            }

            return cacheValue;
        }

        /// <summary>
        /// 获取设置的值
        /// </summary>
        /// <param name="foreignId"></param>
        /// <param name="type"></param>
        /// <param name="instance">如果不支持new()，则需要自己创建一个模型，或者定义初始值</param>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public T GetSettingValue<T>(int foreignId, SettingTypeEnum type, T instance = default)
        {
            return GetSettingValueAsync(foreignId, type, instance).GetAwaiter().GetResult();
        }

        private string GetSettingKey(int foreignId, SettingTypeEnum type)
        {
            var cacheKey = $"{RedisCacheKeys.SettingInfo}:{foreignId}:{type}";
            return cacheKey;
        }

        private delegate T SettingValueDelegate<T>(T instance, List<SettingEntity> settings);

        private static ConcurrentDictionary<string, Delegate> _settingCache = new();

        /// <summary>
        /// 动态创建模型属性赋值的委托
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        private SettingValueDelegate<T> SettingValueAction<T>()
        {
            var type = typeof(T);
            // Check if the delegate is already cached
            if (_settingCache.TryGetValue(type.Name, out var setterAction))
            {
                return setterAction as SettingValueDelegate<T>;
            }

            // Create a dynamic method to set the properties of the model
            var dynamicMethod = new DynamicMethod($"{type.Name}SetProperties", type, new[] { type, typeof(List<SettingEntity>) }, type.Module);

            var il = dynamicMethod.GetILGenerator();

            // Define local variables
            var model = il.DeclareLocal(type); // var model = new T();
            var enumerator =
                il.DeclareLocal(typeof(List<SettingEntity>.Enumerator)); // var enumerator = list.GetEnumerator();
            var item = il.DeclareLocal(typeof(SettingEntity)); // SettingEntity item;
            var condition = il.DeclareLocal(typeof(bool)); // bool condition;

            // Check if instance is null and create a new instance if necessary
            var instanceNotNull = il.DefineLabel();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Brtrue_S, instanceNotNull);
            il.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes)!);
            il.Emit(OpCodes.Starg_S, 0);
            il.MarkLabel(instanceNotNull);

            // Load the instance into the local variable
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Stloc, model);

            // Get the enumerator of the list
            il.Emit(OpCodes.Ldarg_1); // list
            il.Emit(OpCodes.Callvirt, typeof(List<SettingEntity>).GetMethod("GetEnumerator")!);
            il.Emit(OpCodes.Stloc, enumerator);

            // Define loop labels
            var loopStart = il.DefineLabel();
            var loopEnd = il.DefineLabel();

            // Start loop
            il.MarkLabel(loopStart);
            il.Emit(OpCodes.Ldloca_S, enumerator);
            il.Emit(OpCodes.Call, typeof(List<SettingEntity>.Enumerator).GetMethod("MoveNext")!);
            il.Emit(OpCodes.Brfalse, loopEnd);

            // Get current item
            il.Emit(OpCodes.Ldloca_S, enumerator);
            il.Emit(OpCodes.Call, typeof(List<SettingEntity>.Enumerator).GetProperty("Current")!.GetGetMethod()!);
            il.Emit(OpCodes.Stloc, item);

            // Iterate through all public instance properties of the model
            foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
            {
                var nextProperty = il.DefineLabel();

                // Check if the current item's Key matches the property name
                il.Emit(OpCodes.Ldloc, item); // item.Key
                il.Emit(OpCodes.Callvirt, typeof(SettingEntity).GetProperty("Key")!.GetGetMethod()!);
                il.Emit(OpCodes.Ldstr, property.Name); // property.Name
                il.Emit(OpCodes.Call, typeof(string).GetMethod("op_Equality", new[] { typeof(string), typeof(string) })!);
                il.Emit(OpCodes.Stloc, condition); // condition = (item.Key == property.Name)

                il.Emit(OpCodes.Ldloc, condition);
                il.Emit(OpCodes.Brfalse, nextProperty);

                // Set the model's property value
                il.Emit(OpCodes.Ldloc, model); // model.Property
                il.Emit(OpCodes.Ldloc, item); // item.Value
                il.Emit(OpCodes.Callvirt, typeof(SettingEntity).GetProperty("Value")!.GetGetMethod()!);

                if (property.PropertyType.IsEnum)
                {
                    // Handle enum type conversion using EnumConvert method
                    il.Emit(OpCodes.Ldloc, item); // item.Value
                    il.Emit(OpCodes.Callvirt, typeof(SettingEntity).GetProperty("Value")!.GetGetMethod()!);
                    il.Emit(OpCodes.Call,
                        typeof(SettingRepository).GetMethod(nameof(EnumConvert),
                            BindingFlags.NonPublic | BindingFlags.Instance)!.MakeGenericMethod(property.PropertyType));
                }
                else
                {
                    // Handle other types conversion
                    il.Emit(OpCodes.Ldtoken, property.PropertyType);
                    il.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle")!);
                    il.Emit(OpCodes.Call, typeof(Convert).GetMethod("ChangeType", new[] { typeof(object), typeof(Type) })!);
                    il.Emit(OpCodes.Unbox_Any, property.PropertyType);
                }

                // model.Property = (PropertyType)Convert.ChangeType(item.Value, property.PropertyType)
                il.Emit(OpCodes.Callvirt, property.GetSetMethod()!);

                il.MarkLabel(nextProperty);
            }

            // Loop back to start
            il.Emit(OpCodes.Br, loopStart);

            // End loop
            il.MarkLabel(loopEnd);
            il.Emit(OpCodes.Ldloca_S, enumerator);
            il.Emit(OpCodes.Constrained, typeof(List<SettingEntity>.Enumerator));
            il.Emit(OpCodes.Callvirt, typeof(IDisposable).GetMethod("Dispose")!);

            // Return model instance
            il.Emit(OpCodes.Ldloc, model);
            il.Emit(OpCodes.Ret);

            // Create delegate and cache it
            var setter = (SettingValueDelegate<T>)dynamicMethod.CreateDelegate(typeof(SettingValueDelegate<T>));
            _settingCache.TryAdd(type.Name, setter);
            return setter;
        }

        private T EnumConvert<T>(string value) where T : struct
        {
            return System.Enum.Parse<T>(value);
        }
    }
}
